package tracker;

import java.util.*;

public class BEncoding {

    public static final class BEncodingException extends Exception {
        BEncodingException(String msg)   { super(msg); }
        BEncodingException(Exception e)  { super(e); }
    }

    public static String encode(Map object) {
        return encode(new StringBuffer(), object).toString();
    }
    public static String encode(List object) {
        return encode(new StringBuffer(), object).toString();
    }

    public static Object decode(String data) throws BEncodingException {
        if (data == null)
            return null;
        try {
            Offset offset = new Offset();
            Object result = decode(data.toCharArray(), offset);
            /*if (offset.offset >= data.length())
                throw new BEncodingException("extra characters at end of encoded string");*/
            return result;
        } catch (BEncodingException e) {
            throw e;
        } catch (Exception e) {
            throw new BEncodingException(e);
        }
    }
    
    public static Object decode(String data,boolean returnIndex) throws BEncodingException {
        if (data == null)
            return null;
        try {
            Offset offset = new Offset();
            Object result = decode(data.toCharArray(), offset,returnIndex);
            /*if (offset.offset >= data.length())
                throw new BEncodingException("extra characters at end of encoded string");*/
            return result;
        } catch (BEncodingException e) {
            throw e;
        } catch (Exception e) {
            throw new BEncodingException(e);
        }
    }


    private static StringBuffer encode(StringBuffer sb, Object object) {
        if (object instanceof Map) {
            SortedMap tree = (object instanceof SortedMap ? (SortedMap) object : new TreeMap((Map) object));
            sb.append('d');
            if (!tree.isEmpty())
                for (Iterator it = tree.entrySet().iterator(); it.hasNext(); ) {
                    Map.Entry entry = (Map.Entry) it.next();
                    if (entry.getKey() != null && entry.getValue() != null) {
                        encode(sb, entry.getKey().toString());
                        encode(sb, entry.getValue());
                    }
                }
            sb.append('e');
        } else if (object instanceof List) {
            Object value;
            sb.append('l');
            for (Iterator it = ((List) object).iterator(); it.hasNext(); )
                if ((value = it.next()) != null)
                    encode(sb, value);
            sb.append('e');
        } else if (object instanceof Long || object instanceof Integer || object instanceof Short || object instanceof Byte) {
            sb.append('i').append(object).append('e');
        } else if (object != null) {
            String value = object.toString();
            sb.append(value.length()).append(':').append(value);
        }
        return sb;
    }

    private static final class Offset {
        int offset;
    }
    
    private static Object decode(char[] buffer, Offset offset,boolean returnIndex) throws BEncodingException {
        Object key, value;
        int test=offset.offset++;
        if (test>=buffer.length){
        	return null;
        }
        char c = buffer[test];
        switch (c) {
            case 'd':
                Map map = new HashMap();
                while ((key = decode(buffer, offset)) != null) {
                	int indexDebut=offset.offset;
                    if ((value = decode(buffer, offset)) == null)
                        throw new BEncodingException("missing dictionary value for key " + key.toString());
                    if(key.equals("info") && returnIndex) {                    	
                    	int[] tab=new int[2];
                    	tab[0]=indexDebut;
                    	tab[1]=offset.offset;
                   	    return tab;                    	
                    }
                    map.put(key.toString(), value);
                }
                return map;

            case 'l':
                List list = new ArrayList();
                while ((key = decode(buffer, offset)) != null)
                    list.add(key);
                return list;

            case 'e':
                return null;

            case 'i':
                return new Long(readLong(buffer, offset, 'e'));

            default:
                offset.offset--;
                long length = readLong(buffer, offset, ':');
                int start = offset.offset;
                offset.offset += length;
                if (start+length>buffer.length){
                	offset.offset=buffer.length;
                	return new String(buffer, start, buffer.length-start-1);                	
                }else{
                	return new String(buffer, start, (int) length);
                }
        }
    }

    private static Object decode(char[] buffer, Offset offset) throws BEncodingException {
    	return decode(buffer, offset,false);
    }

    private static long readLong(char[] buffer, Offset offset, char terminator) {
        int start = offset.offset;
        while (buffer[offset.offset++] != terminator) ;
        return Long.parseLong(new String(buffer, start, offset.offset - start - 1));
    }

}
